spb/wp-spboucher-ai
Public
1import Link from "next/link";2import { ArrowRight, Search } from "lucide-react";3import { getLocale } from "@/lib/i18n/server";4import { fmtDate, href, t } from "@/lib/i18n";5import { domainsWithCounts, graph, listPapers, stats } from "@/lib/papers/queries";6import { PaperCard, PaperRow } from "@/components/paper-items";7import { StatTiles } from "@/components/stat-tiles";8import { Constellation, type GEdge, type GNode } from "@/components/constellation";9export const revalidate = 300;1011export default async function Home() {12 const locale = await getLocale(); const d = t(locale);13 const [s, featured, latest, domains, g] = await Promise.all([stats(), listPapers({ featured: true, sort: "recent", perPage: 4 }), listPapers({ sort: "revised", perPage: 6 }), domainsWithCounts(), graph(["topic", "method", "dataset", "author", "semantic", "curated"])]);14 const featuredItems = [...featured.items].sort((a, b) => (a.featured_rank ?? 99) - (b.featured_rank ?? 99));15 return (16 <>17 <section className="border-b border-line bg-bg-2/60">18 <div className="container py-14 sm:py-20 grid gap-8 lg:grid-cols-[1.4fr_1fr] items-end">19 <div>20 <p className="kicker mb-3">{d.site.secondary} · {d.site.affiliation}</p>21 <h1 className="text-4xl sm:text-5xl leading-[1.08] text-fg">{d.home.heroTitle}</h1>22 <p className="mt-5 text-lg text-fg-2 max-w-2xl leading-relaxed">{d.home.heroLead}</p>23 <form action={href(locale, "/search")} method="get" role="search" className="mt-7 flex gap-2 max-w-2xl">24 <label htmlFor="home-q" className="sr-only">{d.nav.search}</label>25 <input id="home-q" name="q" className="input" placeholder={d.home.searchPlaceholder} autoComplete="off" />26 <button type="submit" className="btn btn-primary shrink-0"><Search size={16} aria-hidden /> <span className="hidden sm:inline">{d.search.go}</span></button>27 </form>28 <div className="mt-5 flex flex-wrap gap-2">29 <Link href={href(locale, "/papers")} className="btn btn-primary no-underline">{d.home.explore} <ArrowRight size={16} aria-hidden /></Link>30 <Link href={href(locale, "/topics")} className="btn no-underline">{d.home.browseTopics}</Link>31 </div>32 </div>33 <div className="text-sm text-fg-3 lg:text-right"><p className="serif text-fg text-lg italic">{d.site.tagline}</p></div>34 </div>35 </section>3637 <section className="container py-12">38 <div className="flex items-end justify-between gap-4 mb-5"><div><h2 className="text-2xl">{d.home.stats}</h2><p className="meta mt-1">{d.home.statsLead}</p></div></div>39 <StatTiles s={s} locale={locale} />40 </section>4142 {featuredItems.length > 0 && (43 <section className="container py-6">44 <div className="flex items-end justify-between gap-4 mb-5"><div><h2 className="text-2xl">{d.home.featured}</h2><p className="meta mt-1">{d.home.featuredLead}</p></div><Link href={href(locale, "/papers")} className="text-sm text-navy no-underline hover:underline whitespace-nowrap">{d.home.viewAll} →</Link></div>45 <div className="grid gap-4 md:grid-cols-2">{featuredItems.map((p) => <PaperCard key={p.id} p={p} locale={locale} />)}</div>46 </section>47 )}4849 <section className="container py-10 grid gap-10 lg:grid-cols-[1.5fr_1fr]">50 <div>51 <div className="flex items-end justify-between gap-4 mb-2"><div><h2 className="text-2xl">{d.home.latest}</h2><p className="meta mt-1">{d.home.latestLead}</p></div><Link href={href(locale, "/papers?sort=revised")} className="text-sm text-navy no-underline hover:underline whitespace-nowrap">{d.home.viewAll} →</Link></div>52 <div className="card px-5">{latest.items.map((p) => <PaperRow key={p.id} p={p} locale={locale} compact />)}</div>53 </div>54 <div>55 <h2 className="text-2xl">{d.home.domains}</h2><p className="meta mt-1 mb-4">{d.home.domainsLead}</p>56 <ul className="grid gap-2">57 {domains.map((dm) => <li key={dm.slug}><Link href={href(locale, `/topics/${dm.slug}`)} className="card px-4 py-3 flex items-center gap-3 no-underline hover:shadow-[var(--shadow-lift)] transition-shadow"><span className="w-2.5 h-2.5 rounded-full shrink-0" style={{ background: dm.accent ?? "var(--navy)" }} /><span className="flex-1 text-fg">{locale === "fr" ? dm.name_fr : dm.name_en}</span><span className="text-xs text-fg-3 font-mono">{dm.n}</span></Link></li>)}58 </ul>59 </div>60 </section>6162 <section className="container py-10">63 <div className="flex items-end justify-between gap-4 mb-5"><div><h2 className="text-2xl">{d.home.constellation}</h2><p className="meta mt-1 max-w-3xl">{d.home.constellationLead}</p></div><Link href={href(locale, "/research-map")} className="text-sm text-navy no-underline hover:underline whitespace-nowrap">{d.home.openMap} →</Link></div>64 <Constellation locale={locale} nodes={g.nodes as unknown as GNode[]} edges={g.edges as unknown as GEdge[]} compact />65 </section>66 <p className="container text-xs text-fg-3 pb-4">{d.stats.latest}: {s.latest ? fmtDate(locale, s.latest, "month") : "—"}</p>67 </>68 );69}70